﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Net.Mail;
using System.Threading;
using System.Threading.Tasks;
using YEA.Infrastructure.Gateway;
using YEA.Utilities.Logging;
using YEA.Utilities.Notifications;
using YEA.Utilities.Notifications.Converter;
using YEA.Utilities.Service.Controller;
using YEA.Utilities.Common;

namespace YEA.Monitor.NotificationManager
{
    
    public class MessageHandler : IMessageConsumer, IDisposable
    {
        public const string SafeHostListAppSetting = "safeEmailHostList";
        private ServiceStatus status;
        private System.Net.Mail.SmtpClient client;
        private List<string> SafeHostList;
        private bool disposed;
        private ILog Logger;

        public IContentAnalyzer Analyzer { get; set; }

        public MessageHandler(string SmtpMailServerName, ILog logger)
            : this(SmtpMailServerName, new ContentAnalyzer(), logger)
        { }
        public MessageHandler(string SmtpMailServerName, IContentAnalyzer analyzer, ILog logger)
        {
            client = new System.Net.Mail.SmtpClient(SmtpMailServerName);
            client.SendCompleted += SendCompletedCallback;
            disposed = false;
            status = ServiceStatus.Initiated;
            Analyzer = analyzer;
            Logger = logger;
            var hosts = UtilitiesHelper.GetConfigValue(SafeHostListAppSetting, "NA", false);
            //TODO: Test the below.
            UtilitiesHelper.ValidateValue(hosts, @"([A-Z0-9.-]+\.[A-Z]{2,4}\s*(,|;)?)+");
            SafeHostList = new List<string>();
            SafeHostList.AddRange( hosts.Split(',',';') );
        }
        ~MessageHandler()
        {
            Dispose(false);
        }
        public virtual void OnChange(object sender, EventArgs<ServiceStatus> args)
        {
            status = args.EventData;
        }

        public void ConsumeMessage(Message message, RabbitMQ.Client.IModel channel, DeliveryHeader header)
        {
            if (disposed)
            {
                channel.BasicReject(header.DeliveryTag, true);
                channel.Dispose();
            }
            else
            {
                var nc = new NotificationConverter();
                try
                {
                    var notification = nc.ConvertMessageToObject(message);

                    ProcessMessage(notification, header.DeliveryTag.ToString());
                    channel.BasicAck(header.DeliveryTag, false);
                }
                catch (Exception ex)
                {
                    Logger.Write( string.Format("An exception occurred, message: {0}, stack trace {1}, source {2}", ex.Message, ex.StackTrace, ex.Source),LogType.Error);
                    channel.BasicReject(header.DeliveryTag, false);
                }

                status = ServiceStatus.InProgress;
            }
        }

        private void ProcessMessage(Notification notification, string deliveryTag)
        {
            switch( status)
            {
                default:
                    var result = Check(notification);
                    if (result > ContentFlag.Ok)
                        SendErrorNotification(ContentFlag.Blocked);
                    else
                        client.SendAsync(notification, deliveryTag);
                    //TODO:
                    //else
                    //   Send a notifcation to the sender that this email failed inspections
                    break;
                case ServiceStatus.Paused :
                case ServiceStatus.Disabled :
                case ServiceStatus.Recovering :
                    Thread.Sleep(1000);
                        ProcessMessage(notification, deliveryTag);
                    break;
                case ServiceStatus.Stopped :
                    break;
            }
        }
               
        public ContentFlag Check(Notification notification)
        {
            var content = notification.Subject + " " + notification.Body;
            var result = Analyzer.Analyze(content);
            bool isOutgoingMail = checkEmailAddresses(notification);
            
            return isOutgoingMail ? result : ContentFlag.Ok;
        }

        private bool checkEmailAddresses(Notification notification)
        {
            MailAddress[] emailList = new MailAddress[notification.To.Count + notification.CC.Count + notification.Bcc.Count];
            notification.To.CopyTo(emailList, 0);
            notification.CC.CopyTo(emailList, emailList.Length);
            notification.Bcc.CopyTo(emailList, emailList.Length);
            bool isOutgoingMail = false;
            Parallel.ForEach<MailAddress>(notification.To, address => { isOutgoingMail |= isOutgoingCheck(address); });
            Parallel.ForEach<MailAddress>(notification.CC, address => { isOutgoingMail |= isOutgoingCheck(address); });
            Parallel.ForEach<MailAddress>(notification.Bcc, address => { isOutgoingMail |= isOutgoingCheck(address); });
            return isOutgoingMail;
        }

        private bool isOutgoingCheck(MailAddress address)
        {
            return !SafeHostList.Contains(address.Host);
        }

        private void SendErrorNotification(ContentFlag contentFlag)
        {
            throw new NotImplementedException();
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        private void Dispose( bool disposing)
        {
            if (!disposed)
            {
                client.Dispose();
            }
            client = null;
            disposed = true;
        }

        private void SendCompletedCallback(object sender, AsyncCompletedEventArgs e)
        {
            var delivertyTag = (string)e.UserState;
            if( e.Cancelled)
                Logger.Write( string.Format("The message {0} has been cancelled", delivertyTag), LogType.Information);
            if (e.Error != null)
                Logger.Write(string.Format("The message {0} encountered an error during transmission, {1}", delivertyTag, e.Error), LogType.Error);
            else
                Logger.Write(string.Format("the message {0} was successfully delivered"), LogType.Debug);
        }

    }
}
